"""
Authentication Service
======================

Business logic for user authentication and authorization.
Handles user registration, login, logout, and token management.
"""

import logging
from typing import Optional, Dict, Any, Tuple
from datetime import datetime, timedelta

from app.models.user import User
from app.utils.database import DatabaseManager
from app.utils.redis_client import RedisClient
from app.utils.jwt_handler import jwt_handler
from app.utils.password_hasher import password_hasher
from app import db

# Initialize logger
logger = logging.getLogger(__name__)

class AuthService:
    """
    Authentication service for user management.
    
    Handles:
    - User registration
    - User login/logout
    - Token generation and validation
    - Password reset
    - Account lockout management
    """
    
    def __init__(self):
        """Initialize authentication service."""
        self.db_manager = DatabaseManager(db)
        self.redis_client = RedisClient(
            host=db.app.config.get('REDIS_HOST', 'localhost'),
            port=db.app.config.get('REDIS_PORT', 6379),
            db=0
        )
    
    def register_user(self, username: str, email: str, password: str,
                     first_name: Optional[str] = None, 
                     last_name: Optional[str] = None) -> Tuple[bool, Dict[str, Any]]:
        """
        Register a new user.
        
        Args:
            username: Unique username
            email: Email address
            password: Plain text password
            first_name: First name (optional)
            last_name: Last name (optional)
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            # Check if user already exists
            existing_user = User.find_by_username(username)
            if existing_user:
                return False, {
                    'error': 'Username already exists',
                    'error_code': 'username_taken'
                }
            
            existing_email = User.find_by_email(email)
            if existing_email:
                return False, {
                    'error': 'Email already registered',
                    'error_code': 'email_taken'
                }
            
            # Create new user
            user = User(
                username=username,
                email=email.lower(),
                first_name=first_name,
                last_name=last_name
            )
            user.set_password(password)
            
            # Save to database
            if not self.db_manager.create(user):
                return False, {
                    'error': 'Failed to create user',
                    'error_code': 'creation_failed'
                }
            
            logger.info(f"User registered successfully: {username}")
            
            # Generate tokens
            tokens = jwt_handler.generate_tokens(
                user_id=user.id,
                username=user.username,
                email=user.email
            )
            
            return True, {
                'message': 'User registered successfully',
                'user': user.to_dict(),
                'tokens': tokens
            }
        
        except ValueError as e:
            logger.warning(f"Registration validation error: {str(e)}")
            return False, {
                'error': str(e),
                'error_code': 'validation_error'
            }
        
        except Exception as e:
            logger.error(f"Registration error: {str(e)}")
            return False, {
                'error': 'Registration failed',
                'error_code': 'internal_error'
            }
    
    def login_user(self, username_or_email: str, password: str, 
                   remember_me: bool = False) -> Tuple[bool, Dict[str, Any]]:
        """
        Authenticate user and generate tokens.
        
        Args:
            username_or_email: Username or email
            password: Plain text password
            remember_me: Extend token expiry if True
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            # Find user by username or email
            user = User.find_by_username(username_or_email) or User.find_by_email(username_or_email)
            
            if not user:
                return False, {
                    'error': 'Invalid credentials',
                    'error_code': 'invalid_credentials'
                }
            
            # Check if account is locked
            if user.is_locked():
                return False, {
                    'error': 'Account is locked due to multiple failed login attempts',
                    'error_code': 'account_locked',
                    'locked_until': user.locked_until.isoformat() if user.locked_until else None
                }
            
            # Check if account is active
            if not user.is_active:
                return False, {
                    'error': 'Account is deactivated',
                    'error_code': 'account_inactive'
                }
            
            # Verify password
            if not user.check_password(password):
                user.increment_failed_login()
                self.db_manager.update(user)
                
                remaining_attempts = max(0, 5 - user.failed_login_attempts)
                
                return False, {
                    'error': 'Invalid credentials',
                    'error_code': 'invalid_credentials',
                    'remaining_attempts': remaining_attempts
                }
            
            # Reset failed login attempts
            user.reset_failed_login()
            user.update_last_login()
            self.db_manager.update(user)
            
            # Generate tokens
            token_expiry = timedelta(days=7) if remember_me else None
            tokens = jwt_handler.generate_tokens(
                user_id=user.id,
                username=user.username,
                email=user.email
            )
            
            # Cache user session
            session_key = f"session:{user.id}"
            session_data = {
                'user_id': user.id,
                'username': user.username,
                'login_time': datetime.utcnow().isoformat(),
                'ip_address': self._get_client_ip()
            }
            
            self.redis_client.cache_set(session_key, session_data, ttl=86400)
            
            logger.info(f"User logged in successfully: {user.username}")
            
            return True, {
                'message': 'Login successful',
                'user': user.to_dict(),
                'tokens': tokens
            }
        
        except Exception as e:
            logger.error(f"Login error: {str(e)}")
            return False, {
                'error': 'Login failed',
                'error_code': 'internal_error'
            }
    
    def logout_user(self, token: str) -> Tuple[bool, Dict[str, Any]]:
        """
        Logout user and invalidate tokens.
        
        Args:
            token: Current access token
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            # Verify token and get user info
            payload = jwt_handler.verify_token(token)
            
            if not payload:
                return False, {
                    'error': 'Invalid token',
                    'error_code': 'invalid_token'
                }
            
            user_id = payload.get('user_id')
            username = payload.get('username')
            
            # Blacklist the token
            jwt_handler.blacklist_token(token)
            
            # Clear user session from cache
            session_key = f"session:{user_id}"
            self.redis_client.delete(session_key)
            
            logger.info(f"User logged out successfully: {username}")
            
            return True, {
                'message': 'Logout successful'
            }
        
        except Exception as e:
            logger.error(f"Logout error: {str(e)}")
            return False, {
                'error': 'Logout failed',
                'error_code': 'internal_error'
            }
    
    def refresh_token(self, refresh_token: str) -> Tuple[bool, Dict[str, Any]]:
        """
        Refresh access token using refresh token.
        
        Args:
            refresh_token: Valid refresh token
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            new_tokens = jwt_handler.refresh_access_token(refresh_token)
            
            if not new_tokens:
                return False, {
                    'error': 'Invalid refresh token',
                    'error_code': 'invalid_refresh_token'
                }
            
            return True, {
                'message': 'Token refreshed successfully',
                'tokens': new_tokens
            }
        
        except Exception as e:
            logger.error(f"Token refresh error: {str(e)}")
            return False, {
                'error': 'Token refresh failed',
                'error_code': 'internal_error'
            }
    
    def request_password_reset(self, email: str) -> Tuple[bool, Dict[str, Any]]:
        """
        Request password reset for user.
        
        Args:
            email: User email address
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            user = User.find_by_email(email)
            
            if not user:
                # Don't reveal if email exists or not
                return True, {
                    'message': 'If the email exists, a reset link has been sent'
                }
            
            # Generate reset token
            reset_token = user.generate_password_reset_token()
            
            # Store reset request in cache
            reset_key = f"password_reset:{user.id}"
            reset_data = {
                'token': reset_token,
                'created_at': datetime.utcnow().isoformat(),
                'used': False
            }
            
            self.redis_client.cache_set(reset_key, reset_data, ttl=3600)  # 1 hour
            
            # TODO: Send email with reset link
            # For now, just log the token
            logger.info(f"Password reset token for {email}: {reset_token}")
            
            return True, {
                'message': 'If the email exists, a reset link has been sent',
                'reset_token': reset_token  # For development only
            }
        
        except Exception as e:
            logger.error(f"Password reset request error: {str(e)}")
            return False, {
                'error': 'Password reset request failed',
                'error_code': 'internal_error'
            }
    
    def reset_password(self, token: str, new_password: str) -> Tuple[bool, Dict[str, Any]]:
        """
        Reset user password using reset token.
        
        Args:
            token: Password reset token
            new_password: New password
        
        Returns:
            Tuple of (success, result_dict)
        """
        try:
            # Verify reset token
            user = User.verify_password_reset_token(token)
            
            if not user:
                return False, {
                    'error': 'Invalid or expired reset token',
                    'error_code': 'invalid_reset_token'
                }
            
            # Check if reset has been used
            reset_key = f"password_reset:{user.id}"
            reset_data = self.redis_client.get(reset_key)
            
            if not reset_data or reset_data.get('used'):
                return False, {
                    'error': 'Reset token has already been used',
                    'error_code': 'token_used'
                }
            
            # Update password
            user.set_password(new_password)
            
            if not self.db_manager.update(user):
                return False, {
                    'error': 'Failed to update password',
                    'error_code': 'update_failed'
                }
            
            # Mark reset as used
            reset_data['used'] = True
            self.redis_client.set(reset_key, reset_data, ex=3600)
            
            # Blacklist all existing tokens for this user
            self._blacklist_user_tokens(user.id)
            
            logger.info(f"Password reset successful for user: {user.username}")
            
            return True, {
                'message': 'Password reset successful'
            }
        
        except ValueError as e:
            return False, {
                'error': str(e),
                'error_code': 'validation_error'
            }
        
        except Exception as e:
            logger.error(f"Password reset error: {str(e)}")
            return False, {
                'error': 'Password reset failed',
                'error_code': 'internal_error'
            }
    
    def _blacklist_user_tokens(self, user_id: int) -> None:
        """
        Blacklist all tokens for a user.
        
        Args:
            user_id: User ID
        """
        try:
            # Find and blacklist all active sessions
            session_pattern = f"session:{user_id}"
            # This would need to be implemented based on your session storage
            pass
        
        except Exception as e:
            logger.error(f"Error blacklisting user tokens: {str(e)}")
    
    def _get_client_ip(self) -> str:
        """
        Get client IP address from request.
        
        Returns:
            str: Client IP address
        """
        try:
            from flask import request
            
            if request.environ.get('HTTP_X_FORWARDED_FOR'):
                return request.environ['HTTP_X_FORWARDED_FOR'].split(',')[0]
            else:
                return request.environ.get('REMOTE_ADDR', 'unknown')
        except Exception:
            return 'unknown'

# Global auth service instance
auth_service = AuthService()